package com.tigger.core.redis.lock.locked;

import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.tigger.core.redis.lock.AbstractLock;
import com.tigger.core.redis.lock.LockInfo;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class RedisReentrantLock extends AbstractLock {

    private static final String DEFAULT_LOCK_KEY = "RedissonRLock.locked";
    private static final long DEFAULT_LOCK_EXPIRES = 3000L;

    private Jedis jedis;

    protected String lockKey;

    // 锁的有效时长(毫秒)
    protected long lockExpires;

    private static final Logger logger = LoggerFactory.getLogger(RedisReentrantLock.class);

    public RedisReentrantLock(Jedis jedis) {
        this(jedis, DEFAULT_LOCK_KEY, DEFAULT_LOCK_EXPIRES);
    }

    public RedisReentrantLock(Jedis jedis, String lockKey) {
        this(jedis, lockKey, DEFAULT_LOCK_EXPIRES);
    }

    public RedisReentrantLock(JedisPool jedisPool) {
        this(jedisPool, DEFAULT_LOCK_KEY, DEFAULT_LOCK_EXPIRES);
    }

    public RedisReentrantLock(Jedis jedis, String lockKey, long lockExpires) {
        this.jedis = jedis;
        this.lockKey = lockKey;
        this.lockExpires = lockExpires;
    }

    public RedisReentrantLock(JedisPool jedisPool, String lockKey, long lockExpires) {
        this.jedis = jedisPool.getResource();
        this.lockKey = lockKey;
        this.lockExpires = lockExpires;
    }

    @Override
    public boolean tryLock() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void release() {
        // TODO Auto-generated method stub

    }

    @Override
    protected boolean lock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt) throws InterruptedException {
        if (interrupt) {
            checkInterruption();
        }

        // 超时控制 的时间可以从本地获取, 因为这个和锁超时没有关系, 只是一段时间区间的控制
        long start = localTimeMillis();
        long timeout = unit.toMillis(time); // if !useTimeout, then it's useless

        while (useTimeout ? !isTimeout(start, timeout) : true) {
            if (interrupt) {
                checkInterruption();
            }

            long lockExpireTime = serverTimeMillis() + lockExpires + 1;// 锁超时时间
            String newLockInfoJson = LockInfo.newForCurrThread(lockExpireTime).toString();
            if (jedis.setnx(lockKey, newLockInfoJson) == 1) { // 条件能成立的唯一情况就是redis中lockKey还未关联value
                // 成功获取到锁, 设置相关标识
                logger.debug("{} get RedissonRLock(new), lockInfo: {}", Thread.currentThread().getName(), newLockInfoJson);
                locked = true;
                return true;
            }

            // value已有值, 但不能说明锁被持有, 因为锁可能expired了
            String currLockInfoJson = jedis.get(lockKey);
            // 若这瞬间锁被delete了
            if (currLockInfoJson == null) {
                continue;
            }

            LockInfo currLockInfo = LockInfo.fromString(currLockInfoJson);
            // 竞争条件只可能出现在锁超时的情况, 因为如果没有超时, 线程发现锁并不是被自己持有, 线程就不会去动value
            if (isTimeExpired(currLockInfo.getExpires())) {
                // 锁超时了
                LockInfo oldLockInfo = LockInfo.fromString(jedis.getSet(lockKey, newLockInfoJson));
                if (oldLockInfo != null && isTimeExpired(oldLockInfo.getExpires())) {
                    // 成功获取到锁, 设置相关标识
                    logger.debug("{} get RedissonRLock(new), lockInfo: {}", Thread.currentThread().getName(), newLockInfoJson);
                    locked = true;
                    return true;
                }
            } else {
                // 锁未超时, 不会有竞争情况
                if (isHeldByCurrentThread(currLockInfo)) { // 当前线程持有
                    // 成功获取到锁, 设置相关标识
                    currLockInfo.setExpires(serverTimeMillis() + lockExpires + 1); // 设置新的锁超时时间
                    currLockInfo.incCount();
                    jedis.set(lockKey, currLockInfo.toString());
                    logger.debug("{} get RedissonRLock(inc), lockInfo: {}", Thread.currentThread().getName(), currLockInfo);
                    locked = true;
                    return true;
                }
            }
        }
        locked = false;
        return false;
    }

    @Override
    protected void unlock0() {
        // TODO Auto-generated method stub

    }

    @Override
    protected boolean isHeldByCurrentThread() {
        // TODO Auto-generated method stub
        return false;
    }

    private void checkInterruption() throws InterruptedException {
        if (Thread.currentThread().isInterrupted()) {
            throw new InterruptedException();
        }
    }

    private long localTimeMillis() {
        return System.currentTimeMillis();
    }

    private boolean isHeldByCurrentThread(LockInfo lockInfo) {
        return lockInfo.isCurrentThread();
    }

    private boolean isTimeExpired(long time) {
        return time < serverTimeMillis();
    }

    private boolean isTimeout(long start, long timeout) {
        return start + timeout < System.currentTimeMillis();
    }

    private long serverTimeMillis() {
        return Long.parseLong(jedis.time().get(0));
    }

}
